
#import "RtorrentXmlRpcClient.h"

//
// get torrent list request
//
@interface RtorrentGetTorrentListRequest : XmlRpcRequest<GetTorrentDownloadListRequest>

//
// torrent list
//
@property(strong, nonatomic, readwrite) NSMutableArray*						torrentList;

//
// download rate
//
@property(assign, nonatomic, readwrite) uint64_t							downloadRate;

//
// upload rate
//
@property(assign, nonatomic, readwrite) uint64_t							uploadRate;

//
// total size
//
@property(assign, nonatomic, readwrite) uint64_t							totalSize;

@end

@implementation RtorrentGetTorrentListRequest

//
// torrent list
//
@synthesize torrentList														= _torrentList;

//
// download rate
//
@synthesize downloadRate													= _downloadRate;

//
// upload rate
//
@synthesize uploadRate														= _uploadRate;

//
// total size
//
@synthesize totalSize														= _totalSize;

//
// init
//
- (id)initWithMethod:(NSString *)methodName
{
	//
	// call super
	//
	self																	= [super initWithMethod:methodName];
	if(!self)
		return nil;
	
	self.torrentList														= [NSMutableArray array];
	
	return self;
}

//
// dealloc
//
- (void)dealloc
{
	self.torrentList														= nil;
	
	[super dealloc];
}

@end

@implementation RtorrentXmlRpcClient

//
// server info
//
@synthesize serverInfo														= _serverInfo;

//
// init with server info
//
- (id)initWithServerInfo:(ServerInfo*)serverInfo
{
	//
	// call super
	//
	self																	= [super init];
	if(!self)
		return nil;
	
	//
	// save server info
	//
	self.serverInfo															= serverInfo;
	
	return self;
}

//
// dealloc
//
- (void)dealloc
{
	self.serverInfo															= nil;
	
	[super dealloc];
}

//
// setup request address
//
- (void)setupRequestAddress:(AsyncHttpRequest*)httpRequest
{
	NSURL* serverUrl														= [NSURL URLWithString:self.serverInfo.serverAddress];
	httpRequest.protocol													= serverUrl.scheme;
	httpRequest.domain														= serverUrl.host;
	httpRequest.port														= [serverUrl.port integerValue];
	httpRequest.relPath														= serverUrl.path;
	httpRequest.authUserName												= self.serverInfo.userName;
	httpRequest.authPassword												= self.serverInfo.password;
}

//
// update torrent
//
- (void)updateTorrent:(TorrentDownloadItem*)torrentInfo getState:(NSInteger)getState isOpen:(NSInteger)isOpen isHashChecking:(NSInteger)isHashChecking getHashing:(NSInteger)getHashing isActive:(NSInteger)isActive
{
	torrentInfo.state														= 0;
	
	if(isOpen)
	{
		torrentInfo.state													|= kTorrentStateStarted;
		if(!getState || !isActive)
			torrentInfo.state												|= kTorrentStatePaused;
	}
	
	if(getHashing)
		torrentInfo.state													|= kTorrentStateHashing;
	
	if(isHashChecking)
		torrentInfo.state													|= kTorrentStateChecking;
	
	if(torrentInfo.message.length && ![torrentInfo.message isEqualToString:@"Tracker: [Tried all trackers.]"])
		torrentInfo.state													|= kTorrentStateError;
}

//
// add torrent link
//
- (void)addTorrent:(NSString*)torrentAddress autoStart:(BOOL)autoStart callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	id xmlRpcCallback = ^void(XmlRpcRequest* xmlRpcRequest)
	{
		if(callback)
			callback((id<TorrentClientRequest>)xmlRpcRequest);
		
		[xmlRpcRequest release];
	};
	
	XmlRpcRequest* xmlRpcRequest											= [[XmlRpcRequest alloc] initWithMethod:autoStart ? @"load_start" : @"load"];
	[self setupRequestAddress:xmlRpcRequest];
	[xmlRpcRequest addParameter:torrentAddress];
	[xmlRpcRequest startWithCallback:xmlRpcCallback];
}

//
// get torrent list
//
- (void)getTorrentListWithCallback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	id getTorrentListCallback = ^void(RtorrentGetTorrentListRequest* getTorrentListRequest)
	{
		for(NSArray* itemInfo in getTorrentListRequest.result)
		{
			TorrentDownloadItem* torrentInfo								= [[[TorrentDownloadItem alloc] init] autorelease];
			torrentInfo.torrentName											= [itemInfo objectAtIndex:0];
			torrentInfo.infoHash											= [itemInfo objectAtIndex:1];
			torrentInfo.size												= [[itemInfo objectAtIndex:3] unsignedLongLongValue];
			torrentInfo.downloadedSize										= [[itemInfo objectAtIndex:4] unsignedLongLongValue];
			torrentInfo.uploadedSize										= [[itemInfo objectAtIndex:5] unsignedLongLongValue];
			torrentInfo.downloadRate										= [[itemInfo objectAtIndex:6] unsignedLongLongValue];
			torrentInfo.uploadRate											= [[itemInfo objectAtIndex:7] unsignedLongLongValue];
			torrentInfo.seederCount											= [[itemInfo objectAtIndex:8] integerValue];
			torrentInfo.peerCount											= [[itemInfo objectAtIndex:9] integerValue];
			torrentInfo.shareRatio											= [[itemInfo objectAtIndex:10] integerValue];
			torrentInfo.totalPeerCount										= [[itemInfo objectAtIndex:11] integerValue] + [[itemInfo objectAtIndex:12] integerValue];
			torrentInfo.totalSeederCount									= torrentInfo.totalPeerCount;
			torrentInfo.message												= [itemInfo objectAtIndex:13];
			torrentInfo.basePath											= [itemInfo objectAtIndex:14];
			NSInteger getState												= [[itemInfo objectAtIndex:2] integerValue];
			NSInteger isOpen												= [[itemInfo objectAtIndex:15] integerValue];
			NSInteger isHashChecking										= [[itemInfo objectAtIndex:16] integerValue];
			NSInteger getHashing											= [[itemInfo objectAtIndex:17] integerValue];
			NSInteger isActive												= [[itemInfo objectAtIndex:18] integerValue];
			torrentInfo.leftTime											= torrentInfo.downloadRate ? (torrentInfo.size - torrentInfo.downloadedSize) / torrentInfo.downloadRate : -1;
			getTorrentListRequest.totalSize									+= torrentInfo.size;
			getTorrentListRequest.downloadRate								+= torrentInfo.downloadRate;
			getTorrentListRequest.uploadRate								+= torrentInfo.uploadRate;
			[self updateTorrent:torrentInfo getState:getState isOpen:isOpen isHashChecking:isHashChecking getHashing:getHashing isActive:isActive];
			
			[getTorrentListRequest.torrentList addObject:torrentInfo];
		}
		
		if(callback)
			callback(getTorrentListRequest);
		
		[getTorrentListRequest release];
	};
	
	RtorrentGetTorrentListRequest* getTorrentListRequest					= [[RtorrentGetTorrentListRequest alloc] initWithMethod:@"d.multicall"];
	[self setupRequestAddress:getTorrentListRequest];
	[getTorrentListRequest addParameter:@"main"];
	[getTorrentListRequest addParameter:@"d.get_name="];
	[getTorrentListRequest addParameter:@"d.get_hash="];
	[getTorrentListRequest addParameter:@"d.get_state="];
	[getTorrentListRequest addParameter:@"d.get_size_bytes="];
	[getTorrentListRequest addParameter:@"d.get_bytes_done="];
	[getTorrentListRequest addParameter:@"d.get_up_total="];
	[getTorrentListRequest addParameter:@"d.get_down_rate="];
	[getTorrentListRequest addParameter:@"d.get_up_rate="];
	[getTorrentListRequest addParameter:@"d.get_peers_complete="];
	[getTorrentListRequest addParameter:@"d.get_peers_accounted="];
	[getTorrentListRequest addParameter:@"d.get_ratio="];
	[getTorrentListRequest addParameter:@"d.get_peers_not_connected="];
	[getTorrentListRequest addParameter:@"d.get_peers_connected="];
	[getTorrentListRequest addParameter:@"d.get_message="];
	[getTorrentListRequest addParameter:@"d.get_base_path="];
	[getTorrentListRequest addParameter:@"d.is_open="];
	[getTorrentListRequest addParameter:@"d.is_hash_checking="];
	[getTorrentListRequest addParameter:@"d.get_hashing="];
	[getTorrentListRequest addParameter:@"d.is_active="];
	[getTorrentListRequest startWithCallback:getTorrentListCallback];
}

//
// check system.multicall
//
- (NSError*)checkSystemMultiCall:(XmlRpcRequest*)systemMultiCall
{
	for(id item in systemMultiCall.result)
	{
		if([systemMultiCall checkFaultDictionary:item])
			break;
	}
	
	return systemMultiCall.error;
}

//
// refresh torrent info
//
- (void)refreshTorrentInfo:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id refreshTorrentCallback = ^void(XmlRpcRequest* refreshTorrentRequest)
	{
		if(!refreshTorrentRequest.error && ![self checkSystemMultiCall:refreshTorrentRequest])
		{
			NSInteger getState												= [[[refreshTorrentRequest.result objectAtIndex:0] objectAtIndex:0] integerValue];
			NSInteger isOpen												= [[[refreshTorrentRequest.result objectAtIndex:1] objectAtIndex:0] integerValue];
			NSInteger isHashChecking										= [[[refreshTorrentRequest.result objectAtIndex:2] objectAtIndex:0] integerValue];
			NSInteger getHashing											= [[[refreshTorrentRequest.result objectAtIndex:3] objectAtIndex:0] integerValue];
			NSInteger isActive												= [[[refreshTorrentRequest.result objectAtIndex:4] objectAtIndex:0] integerValue];
			torrentInfo.message												= [[refreshTorrentRequest.result objectAtIndex:5] objectAtIndex:0];
			
			[self updateTorrent:torrentInfo getState:getState isOpen:isOpen isHashChecking:isHashChecking getHashing:getHashing isActive:isActive];
		}
		
		if(callback)
			callback((id<TorrentClientRequest>)refreshTorrentRequest);
		
		[refreshTorrentRequest release];
	};
	
	XmlRpcRequest* refreshTorrentRequest									= [[XmlRpcRequest alloc] initWithMethod:@"system.multicall"];
	NSArray* infoHash														= [NSArray arrayWithObject:torrentInfo.infoHash];
	NSDictionary* getStateRequest											= [NSDictionary dictionaryWithObjectsAndKeys:@"d.get_state", @"methodName", infoHash, @"params", nil];
	NSDictionary* isOpenRequest												= [NSDictionary dictionaryWithObjectsAndKeys:@"d.is_open", @"methodName", infoHash, @"params", nil];
	NSDictionary* isHashCheckingRequest										= [NSDictionary dictionaryWithObjectsAndKeys:@"d.is_hash_checking", @"methodName", infoHash, @"params", nil];
	NSDictionary* getHashingRequest											= [NSDictionary dictionaryWithObjectsAndKeys:@"d.get_hashing", @"methodName", infoHash, @"params", nil];
	NSDictionary* isActiveRequest											= [NSDictionary dictionaryWithObjectsAndKeys:@"d.is_active", @"methodName", infoHash, @"params", nil];
	NSDictionary* messageRequest											= [NSDictionary dictionaryWithObjectsAndKeys:@"d.message", @"methodName", infoHash, @"params", nil];
	[refreshTorrentRequest addParameter:[NSArray arrayWithObjects:getStateRequest, isOpenRequest, isHashCheckingRequest, getHashingRequest, isActiveRequest, messageRequest, nil]];
	[self setupRequestAddress:refreshTorrentRequest];
	[refreshTorrentRequest startWithCallback:refreshTorrentCallback];
}

//
// start torrent
//
- (void)startTorrent:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id startTorrentCallback = ^void(XmlRpcRequest* startTorrentRequest)
	{
		if(!startTorrentRequest.error && ![self checkSystemMultiCall:startTorrentRequest])
			[self refreshTorrentInfo:torrentInfo callback:callback];
		else if(callback)
			callback((id<TorrentClientRequest>)startTorrentRequest);
		
		[startTorrentRequest release];
	};
	
	XmlRpcRequest* startTorrentRequest										= [[XmlRpcRequest alloc] initWithMethod:@"system.multicall"];
	NSArray* infoHash														= [NSArray arrayWithObject:torrentInfo.infoHash];
	NSDictionary* openRequest												= [NSDictionary dictionaryWithObjectsAndKeys:@"d.open", @"methodName", infoHash, @"params", nil];
	NSDictionary* startRequest												= [NSDictionary dictionaryWithObjectsAndKeys:@"d.start", @"methodName", infoHash, @"params", nil];
	[startTorrentRequest addParameter:[NSArray arrayWithObjects:openRequest, startRequest, nil]];
	[self setupRequestAddress:startTorrentRequest];
	[startTorrentRequest startWithCallback:startTorrentCallback];
}

//
// pause torrent
//
- (void)pauseTorrent:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id pauseTorrentCallback = ^void(XmlRpcRequest* pauseTorrentRequest)
	{
		if(!pauseTorrentRequest.error)
			[self refreshTorrentInfo:torrentInfo callback:callback];
		else if(callback)
			callback((id<TorrentClientRequest>)pauseTorrentRequest);
		
		[pauseTorrentRequest release];
	};
	
	XmlRpcRequest* pauseTorrentRequest										= [[XmlRpcRequest alloc] initWithMethod:@"d.stop"];
	[pauseTorrentRequest addParameter:torrentInfo.infoHash];
	[self setupRequestAddress:pauseTorrentRequest];
	[pauseTorrentRequest startWithCallback:pauseTorrentCallback];
}

//
// stop torrent
//
- (void)stopTorrent:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id stopTorrentCallback = ^void(XmlRpcRequest* stopTorrentRequest)
	{
		if(!stopTorrentRequest.error && ![self checkSystemMultiCall:stopTorrentRequest])
			[self refreshTorrentInfo:torrentInfo callback:callback];
		else if(callback)
			callback((id<TorrentClientRequest>)stopTorrentRequest);
		
		[stopTorrentRequest release];
	};
	
	XmlRpcRequest* stopTorrentRequest										= [[XmlRpcRequest alloc] initWithMethod:@"system.multicall"];
	NSArray* infoHash														= [NSArray arrayWithObject:torrentInfo.infoHash];
	NSDictionary* stopRequest												= [NSDictionary dictionaryWithObjectsAndKeys:@"d.stop", @"methodName", infoHash, @"params", nil];
	NSDictionary* closeRequest												= [NSDictionary dictionaryWithObjectsAndKeys:@"d.close", @"methodName", infoHash, @"params", nil];
	[stopTorrentRequest addParameter:[NSArray arrayWithObjects:stopRequest, closeRequest, nil]];
	[self setupRequestAddress:stopTorrentRequest];
	[stopTorrentRequest startWithCallback:stopTorrentCallback];
}

//
// recheck torrent
//
- (void)recheckTorrent:(TorrentDownloadItem*)torrentInfo callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id recheckTorrentCallback = ^void(XmlRpcRequest* recheckTorrentRequest)
	{
		if(!recheckTorrentRequest.error)
			[self refreshTorrentInfo:torrentInfo callback:callback];
		else if(callback)
			callback((id<TorrentClientRequest>)recheckTorrentRequest);
		
		[recheckTorrentRequest release];
	};
	
	XmlRpcRequest* recheckTorrentRequest									= [[XmlRpcRequest alloc] initWithMethod:@"d.check_hash"];
	[recheckTorrentRequest addParameter:torrentInfo.infoHash];
	[self setupRequestAddress:recheckTorrentRequest];
	[recheckTorrentRequest startWithCallback:recheckTorrentCallback];
}

//
// remove torrent
//
- (void)removeTorrent:(TorrentDownloadItem*)torrentInfo eraseData:(BOOL)eraseData callback:(TorrentClientFinishCallback)callback
{
	callback																= [[callback copy] autorelease];
	
	id removeTorrentCallback = ^void(XmlRpcRequest* removeTorrentRequest)
	{
		if(!removeTorrentRequest.error)
			[self checkSystemMultiCall:removeTorrentRequest];
		
		if(callback)
			callback((id<TorrentClientRequest>)removeTorrentRequest);
		
		[removeTorrentRequest release];
	};

	
	XmlRpcRequest* removeTorrentRequest										= [[XmlRpcRequest alloc] initWithMethod:@"system.multicall"];
	NSArray* infoHash														= [NSArray arrayWithObject:torrentInfo.infoHash];
	NSDictionary* eraseRequest												= [NSDictionary dictionaryWithObjectsAndKeys:@"d.erase", @"methodName", infoHash, @"params", nil];
	if(eraseData && torrentInfo.basePath.length)
	{
		NSArray* rmCommand													= [NSArray arrayWithObjects:@"rm", @"-rf", torrentInfo.basePath, nil];
		NSDictionary* deleteRequest											= [NSDictionary dictionaryWithObjectsAndKeys:@"execute", @"methodName", rmCommand, @"params", nil];
		[removeTorrentRequest addParameter:[NSArray arrayWithObjects:eraseRequest, deleteRequest, nil]];
	}
	else
	{
		[removeTorrentRequest addParameter:[NSArray arrayWithObject:eraseRequest]];
	}

	[self setupRequestAddress:removeTorrentRequest];
	[removeTorrentRequest startWithCallback:removeTorrentCallback];
}

@end
